<!DOCTYPE html>
<html>
  <head>
    <title>
      Test Automation of AudioListener
    </title>
    <script src="../../resources/testharness.js"></script>
    <script src="../../resources/testharnessreport.js"></script>
    <script src="../resources/audit-util.js"></script>
    <script src="../resources/audit.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      let sampleRate = 48000;
      // These tests are quite slow, so don't run for many frames.  1024 frames
      // should be enough to demonstrate that automations are working.
      let renderFrames = 1024;
      let renderDuration = renderFrames / sampleRate;

      // End time of the automation.  Fairly arbitrary, but must be less than
      // the render duration so that we can verify that the automation did end
      // at the correct time and value.
      let endTime = 0.75 * renderDuration;

      let audit = Audit.createTaskRunner();

      // Starting position for the panner
      let defaultStartPosition = {x: 0, y: 0, z: 0};

      // Ending position for the panner
      let pannerEndPosition = {x: 1000, y: 1000, z: 1000};

      // Ending position for the listener.  It MUST be the the negative of the
      // pannerEndPosition!
      let listenerEndPosition = {
        x: -pannerEndPosition.x,
        y: -pannerEndPosition.y,
        z: -pannerEndPosition.z
      };


      // Test the linear, inverse, and exponential distance models when the
      // AudioListener is moving instead of the panner.  We take advantage that
      // motion is relative and create a reference by moving the panner in one
      // direction.  The resulting output is the expected result.  Then we redo
      // the test, but this time move the listener in the opposite direction.
      // The output of this test is compared against the panner result.  They
      // should be the same.

      audit.define('linear', (task, should) => {
        runTest({
          should: should,
          startPosition: defaultStartPosition,
          endPosition: pannerEndPosition,
          distanceModel: {model: 'linear', rolloff: 1}
        }).then(() => task.done());
      });

      audit.define('exponential', (task, should) => {
        runTest({
          should: should,
          startPosition: defaultStartPosition,
          endPosition: pannerEndPosition,
          distanceModel: {model: 'exponential', rolloff: 1.5}
        }).then(() => task.done());
      });

      audit.define('inverse', (task, should) => {
        runTest({
          should: should,
          startPosition: defaultStartPosition,
          endPosition: pannerEndPosition,
          distanceModel: {model: 'inverse', rolloff: 1}
        }).then(() => task.done());
      });

      audit.run();

      function createGraph() {
        let context = new OfflineAudioContext(2, renderFrames, sampleRate);
        // Stereo source for the panner.
        let source = context.createBufferSource();
        source.buffer = createConstantBuffer(context, renderFrames, [1, 2]);

        let panner = context.createPanner();
        panner.panningModel = 'equalpower';

        source.connect(panner);
        panner.connect(context.destination);

        return {context: context, source: source, panner: panner};
      }

      // Run a listener test with the given options.
      function runTest(options) {
        let refResult;

        return runPannerTest(options)
            .then(function(renderedBuffer) {
              refResult = renderedBuffer;
            })
            .then(function() {
              // Move the listener in the opposite direction.
              options.endPosition = listenerEndPosition;
              return runListenerTest(options).then(function(renderedBuffer) {
                compareResults(renderedBuffer, refResult, options);
              });
            });
      }

      function runPannerTest(options) {
        let graph = createGraph();

        return runTestCore(graph, options, graph.panner);
      }


      function runListenerTest(options) {
        let graph = createGraph();

        return runTestCore(graph, options, graph.context.listener);
      }

      function runTestCore(graph, options, audioOjbect) {
        let {context, source, panner} = graph;

        // Initialize the panner with known values.
        panner.distanceModel = options.distanceModel.model;
        panner.rolloffFactor = options.distanceModel.rolloff;
        panner.panningModel = 'equalpower';

        // Automate the location.  audioObject must be either a PannerNode or
        // the context's AudioListener.

        audioOjbect.positionX.setValueAtTime(options.startPosition.x, 0);
        audioOjbect.positionY.setValueAtTime(options.startPosition.y, 0);
        audioOjbect.positionZ.setValueAtTime(options.startPosition.z, 0);

        audioOjbect.positionX.linearRampToValueAtTime(
            options.endPosition.x, endTime);
        audioOjbect.positionY.linearRampToValueAtTime(
            options.endPosition.y, endTime);
        audioOjbect.positionZ.linearRampToValueAtTime(
            options.endPosition.z, endTime);

        // Start the source, render the graph, and return the resulting promise
        // from rendering.
        source.start();

        return context.startRendering().then(function(resultBuffer) {
          return resultBuffer;
        });
      }

      function compareResults(actualResult, expectedResult, options) {
        // Compare the output with the reference output from moving the source
        // in the opposite direction.

        let expectedLeft = expectedResult.getChannelData(0);
        let expectedRight = expectedResult.getChannelData(1);

        let actualLeft = actualResult.getChannelData(0);
        let actualRight = actualResult.getChannelData(1);

        let prefix = 'Distance model: "' + options.distanceModel.model + '"';
        prefix += ', rolloff: ' + options.distanceModel.rolloff;
        options.should(actualLeft, prefix + ': left channel')
            .beCloseToArray(expectedLeft, 0);
        options.should(actualRight, prefix + ': right channel')
            .beCloseToArray(expectedRight, 0);
      }
    </script>
  </body>
</html>
